package cn.csc.framework.pay.core.client.impl.tl;

import cn.csc.framework.pay.core.client.PayCommonResult;
import cn.csc.framework.pay.core.client.dto.*;
import cn.csc.framework.pay.core.enums.PayNotifyRefundStatusEnum;
import cn.csc.framework.pay.core.enums.PayTypeEnum;
import cn.csc.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.csc.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static cn.csc.framework.common.util.json.JsonUtils.toJsonString;
import static cn.hutool.core.date.DatePattern.*;

@Slf4j
public class TLLitePayClient extends AbstractTLClient {

    public TLLitePayClient(BigInteger channelId, String channelCode, TLPayClientConfig config) {
        super(channelId, channelCode, config, new TLPayCodeMapping());
    }

    @Override
    protected PayCommonResult<PayOrderUnifiedRespDTO> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
        try {
            Map<String, String> map = payService.pay(reqDTO.getAmount(),
                    reqDTO.getMerchantOrderId(),
                    this.getChannelCode(),
                    reqDTO.getSubject() == null ? "" : reqDTO.getSubject(),
                    reqDTO.getRemark() == null ? "" : reqDTO.getRemark(),
                    reqDTO.getChannelExtras().get("openId"),
                    "",
                    reqDTO.getNotifyUrl(),
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    "",
                    reqDTO.getUserIp(),
                    "",
                    config.getOrgId(),
                    config.getMchId(),
                    config.getAppId(),
                    config.getSignType(),
                    config.getPrivateKey());
            PayOrderUnifiedRespDTO respDTO = PayOrderUnifiedRespDTO.builder()
                    .displayContent(map.get("payinfo")).displayMode("")
                    .tradeNo(map.get("reqsn"))
                    .rawData(toJsonString(map)).build();

            return PayCommonResult.build(map.get("retcode"), map.get("retmsg"), respDTO, codeMapping);
        } catch (Exception e) {
            log.error("[[doUnifiedOrder][request({})调用通联支付异常]", toJsonString(reqDTO), e);
            return PayCommonResult.build("-99999", "发起下单失败,网络读超时，状态未知", null, codeMapping);
        }
    }

    @Override
    protected PayOrderUnifiedRespDTO doUnifiedBeanOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable {
        Map<String, String> map = payService.pay(reqDTO.getAmount(),
                reqDTO.getMerchantOrderId(),
                this.getChannelCode(),
                reqDTO.getSubject() == null ? "" : reqDTO.getSubject(),
                reqDTO.getRemark() == null ? "" : reqDTO.getRemark(),
                reqDTO.getChannelExtras().get("openId"),
                "",
                reqDTO.getNotifyUrl(),
                "",
                "",
                "",
                "",
                StringUtils.hasLength(reqDTO.getChannelExtras().get("subAppId")) ? reqDTO.getChannelExtras().get("subAppId") : "",
                "",
                "",
                "",
                "",
                "",
                reqDTO.getUserIp(),
                "");
        // 支付成功
        if ("SUCCESS".equals(map.get("retcode")) && "0000".equals(map.get("trxstatus"))) {
            Map<String, String> payInfo = new HashMap<>();
            if ("W01".equals(this.getChannelCode()))
                payInfo.put("qrcode", map.get("payinfo"));
            else
                payInfo.putAll(JSONObject.parseObject(map.get("payinfo"), Map.class));
            // 通联返回fintime格式：yyyyMMddHHmmss，转换为yyyy-MM-dd HH:mm:ss
            String finTime = LocalDateTimeUtil.formatNormal(LocalDateTimeUtil.parse(map.get("fintime"), PURE_DATETIME_FORMATTER));
            return PayOrderUnifiedRespDTO.successOf(map.get("trxid"), "", finTime,
                    map.get("trxid"), map.get("reqsn"), toJsonString(map), payInfo)
                    .setDisplayContent(map.get("payinfo"));
        }
        PayOrderUnifiedRespDTO respDTO = PayOrderUnifiedRespDTO.closedOf(map.get("retcode"), map.get("retmsg"), "", toJsonString(map));
        if (map.containsKey("trxstatus")){
            respDTO.setTradeStatus(map.get("trxstatus"));
        }
        return respDTO;
    }

    @Override
    protected PayRefundUnifiedRespDTO doUnifiedBeanRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
        JSONObject refund = payService.refund(
                reqDTO.getRefundAmount(),
                reqDTO.getMerchantRefundId(),
                "",
                reqDTO.getPayTradeNo(),
                config.getOrgId(),
                config.getMchId(),
                config.getAppId(),
                config.getSignType(),
                config.getPrivateKey());

        // 退款成功
        if ("SUCCESS".equals(refund.getString("retcode")) && "0000".equals(refund.getString("trxstatus"))) {
            // 通联fintime返回：20240219105024
            String finTime = LocalDateTimeUtil.formatNormal(LocalDateTimeUtil.parse(refund.getString("fintime"), PURE_DATETIME_FORMATTER));
            return PayRefundUnifiedRespDTO.successOf(refund.getString("chnltrxid"), finTime
                    , refund.getString("reqsn"), refund);
        }
        PayRefundUnifiedRespDTO respDTO = PayRefundUnifiedRespDTO.failureOf(refund.getString("retcode"), refund.getString("retmsg")
                , null, refund);
        if (refund.containsKey("trxstatus")){
            respDTO.setTradeStatus(refund.getString("trxstatus"));
        }
        return respDTO;
    }

    /**
     * 从支付宝通知返回参数中解析 PayOrderNotifyRespDTO, 通知具体参数参考
     * //https://opendocs.alipay.com/open/203/105286
     *
     * @param data 通知结果
     * @return 解析结果 PayOrderNotifyRespDTO
     * @throws Exception 解析失败，抛出异常
     */
    @Override
    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
        Map<String, String> params = data.getParams();
        Map<String, String> bodyObj = HttpUtil.decodeParamMap(data.getBody(), StandardCharsets.UTF_8);

        return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("cusorderid"))
                .channelOrderNo(bodyObj.get("trxid"))
                .tradeStatus("trxstatus").successTime(parseMuchTime(params.get("paytime")))
                .build();
    }

    @Override
    public PayRefundNotifyRespDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
        Map<String, String> params = strToMap(notifyData.getBody());
        PayRefundNotifyRespDTO notifyDTO = PayRefundNotifyRespDTO.builder().channelOrderNo(params.get("trade_no"))
                .tradeNo(params.get("out_trade_no"))
                .reqNo(params.get("out_biz_no"))
                .status(PayNotifyRefundStatusEnum.SUCCESS)
                .refundSuccessTime(LocalDateTimeUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
                .rawData(notifyData.getBody())
                .build();
        return notifyDTO;
    }

    @Override
    public boolean isRefundNotify(PayNotifyDataDTO notifyData) {
        if (notifyData.getParams().containsKey("refund_fee")) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
        boolean verifyResult = false;
        try {

        } catch (Exception e) {
            log.error("[TLPayClient verifyNotifyData][(notify param is :{}) 验证失败]", toJsonString(notifyData.getParams()), e);
        }
        return verifyResult;
    }

    @Override
    protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {

        try {
            JSONObject refund = payService.refund(
                    reqDTO.getRefundAmount(),
                    reqDTO.getMerchantRefundId(),
                    "",
                    reqDTO.getPayTradeNo(),
                    config.getOrgId(),
                    config.getMchId(),
                    config.getAppId(),
                    config.getSignType(),
                    config.getPrivateKey());

            PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
            respDTO.setFinTime(refund.getString("fintime"));
            respDTO.setRefundType(PayTypeEnum.TLPAY.getStatus());
            respDTO.setChannelRefundId(refund.getString("trxid"));
            return PayCommonResult.build(refund.getString("trxstatus"), refund.getString("errmsg"), respDTO, codeMapping);
        } catch (Exception e) {
            // TODO 记录异常日志
            log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时，退款状态未知]", toJsonString(reqDTO), e);
            return PayCommonResult.build("-99999", "发起退款失败,网络读超时，退款状态未知", null, codeMapping);
        }
    }

    /**
     * 支付宝统一回调参数  str 转 map
     *
     * @param s 支付宝支付通知回调参数
     * @return map 支付宝集合
     */
    public static Map<String, String> strToMap(String s) {
        // TODO @zxy：这个可以使用 hutool 的 HttpUtil decodeParams 方法么？
        Map<String, String> stringStringMap = new HashMap<>();
        // 调整时间格式
        String s3 = s.replaceAll("%3A", ":");
        // 获取 map
        String s4 = s3.replace("+", " ");
        String[] split = s4.split("&");
        for (String s1 : split) {
            String[] split1 = s1.split("=");
            stringStringMap.put(split1[0], split1[1]);
        }
        return stringStringMap;
    }

    @Override
    public Object parseNotify(PayNotifyReqDTO rawNotify) {
        // 1. 校验回调数据
        String body = rawNotify.getBody();
        Map<String, String> params = rawNotify.getParams();
        Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);


        // 交易类型：https://aipboss.allinpay.com/know/devhelp/main.php?pid=15&from=groupmessage&isappinstalled=0#mid=97
        // VSP502:微信支付撤销  VSP503:微信支付退款  VSP512:支付宝支付撤销  VSP513:支付宝支付退款
        // VSP542:扫码撤销 VSP543:扫码退货  VSP552:银联扫码撤销  VSP553:银联扫码退货
        // 2.1 退款的情况
        List<String> refundTrxCodes = Arrays.asList("VSP502", "VSP503", "VSP512", "VSP513", "VSP542", "VSP543", "VSP552", "VSP553");
        LocalDateTime payTime = LocalDateTimeUtil.parse(bodyObj.get("paytime"), PURE_DATETIME_PATTERN);
        Set<String> refundSet = refundTrxCodes.stream().collect(Collectors.toSet());
        if (bodyObj.containsKey("trxcode") && refundSet.contains(bodyObj.get("trxcode"))) {
            return PayRefundUnifiedRespDTO.builder()
                    .status(PayRefundStatusRespEnum.SUCCESS.getStatus())
                    .outRefundNo(bodyObj.get("cusorderid"))
                    .channelRefundId(bodyObj.get("trxid"))
                    .finTime(LocalDateTimeUtil.format(payTime, NORM_DATETIME_PATTERN))
                    .rawData(body)
                    .build();
        }
        // 2.2 支付的情况
        // VSP501:微信支付   VSP511:支付宝支付  VSP541：扫码支付  VSP551：银联扫码支付
        List<String> payTrxCodes = Arrays.asList("VSP501", "VSP511", "VSP541", "VSP551");
        Set<String> paySet = payTrxCodes.stream().collect(Collectors.toSet());
        // bodyObj.get("paytime")格式为20240131113317
        if (bodyObj.containsKey("trxcode") && paySet.contains(bodyObj.get("trxcode"))) {
            return PayOrderUnifiedRespDTO.builder()
                    .status(PayOrderStatusRespEnum.SUCCESS.getStatus())
                    .merchantOrderNo(params.get("trxid"))
                    .tradeNo(bodyObj.get("cusorderid"))
                    .channelOrderNo(params.get("trxid"))
                    .successTime(LocalDateTimeUtil.format(payTime, NORM_DATETIME_PATTERN))
                    .rawData(body)
                    .build();
        }
        log.warn("[parseNotify]未知通知：{}", rawNotify);
        return null;
    }

    protected LocalDateTime parseMuchTime(String str) {
        LocalDateTime t1 = LocalDateTimeUtil.parse(str, PURE_DATETIME_FORMATTER);
        String t2 = LocalDateTimeUtil.format(t1, NORM_DATETIME_FORMATTER);
        return LocalDateTimeUtil.parse(t2, NORM_DATETIME_FORMATTER);
    }


}
